package net.loogn.stardust.client;

import com.alibaba.fastjson.JSON;
import net.loogn.stardust.common.enums.NodeEventType;
import net.loogn.stardust.common.enums.ServerNodeStatus;
import net.loogn.stardust.common.model.*;
import net.loogn.stardust.common.utils.HttpHelper;
import org.apache.calcite.linq4j.Linq4j;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Created by Administrator on 2017/4/6.
 */
public class NodeManager {

    static String getNodesByNameUrl() {
        return StardustClient.getConfigCenterUrl() + "/StardustConfigCenter/GetNodesByName";
    }

    static String getNodesUpdatesUrl() {
        return StardustClient.getConfigCenterUrl() + "/StardustConfigCenter/GetNodesUpdates";
    }

    static Timer s_timer;

    static {
        s_timer = new Timer();
        s_timer.schedule(new GetNodesUpdatesTimerTask(), 1000, 6000);
    }

    static ConcurrentHashMap<String, NodeGroupDTO> m_nodeDict = new ConcurrentHashMap<String, NodeGroupDTO>();

    public static ServerNodeModel getNode(String serviceName, String version) throws Exception {
        NodeGroupDTO group = m_nodeDict.getOrDefault(serviceName, null);
        if (group == null) {
            group = loadByServiceName(serviceName);
            m_nodeDict.put(serviceName, group);
        }
        group.setLastInvokeTime(new Date());
        return chooseNode(group.getNodes(), serviceName, version);
    }


    static ServerNodeModel chooseNode(List<ServerNodeModel> nodes, String serviceName, String version) throws Exception {
        if (nodes == null || nodes.size() == 0) {
            new Exception(String.format("服务：【%s】没有兼容的节点版本【%s】", serviceName, version)).printStackTrace();
        }

        String[] versionParts = version.split("\\.");
        String v1 = versionParts[0];
        String v2 = versionParts[1];
        final String compatibleVersionPrex = v1 + ".";
        List<ServerNodeModel> compatibleNodes = Linq4j.asEnumerable(nodes)
                .where(x -> x.getStatus() == ServerNodeStatus.Normal && x.getVersion().startsWith(compatibleVersionPrex)).toList();
        if (compatibleNodes.size() == 0) {
            throw new Exception(String.format("服务：【%s】没有兼容的节点版本【%s】", serviceName, version));
        }
        List<ServerNodeModel> balanceNodes;
        if (v2.equals("*")) {
            balanceNodes = Linq4j.asEnumerable(compatibleNodes).groupBy(x -> x.getVersion())
                    .orderByDescending(x -> Integer.parseInt(x.getKey().split("\\.")[1])).first().toList();
        } else if (v2.endsWith("+")) {
            int intV2 = Integer.parseInt(v2.replace("+", ""));
            balanceNodes = Linq4j.asEnumerable(compatibleNodes).where(x -> Integer.parseInt(x.getVersion().split("\\.")[1]) >= intV2).toList();
        } else if (v2.endsWith("-")) {
            int intV2 = Integer.parseInt(v2.replace("-", ""));
            balanceNodes = Linq4j.asEnumerable(compatibleNodes).where(x -> Integer.parseInt(x.getVersion().split("\\.")[1]) <= intV2).toList();
        } else if (v2.endsWith(">")) {
            int intV2 = Integer.parseInt(v2.replace(">", ""));
            balanceNodes = Linq4j.asEnumerable(compatibleNodes).where(x -> Integer.parseInt(x.getVersion().split("\\.")[1]) > intV2).toList();
        } else if (v2.endsWith("<")) {
            int intV2 = Integer.parseInt(v2.replace("<", ""));
            balanceNodes = Linq4j.asEnumerable(compatibleNodes).where(x -> Integer.parseInt(x.getVersion().split("\\.")[1]) < intV2).toList();
        } else {
            balanceNodes = Linq4j.asEnumerable(compatibleNodes).where(x -> x.getVersion().equals(version)).toList();
        }

        return balanceNode(balanceNodes, serviceName, version);
    }

    static ServerNodeModel balanceNode(List<ServerNodeModel> nodes, String serviceName, String version) throws Exception {
        if (nodes.size() == 0) {
            throw new Exception(String.format("服务：【%s】没有兼容的节点版本【%s】", serviceName, version));
        }
        if (nodes.size() == 1) return nodes.get(0);

        double averageWeight = 1d / nodes.size();
        double totalWeight = 0d;
        //处理权重 ,DynamicWeight
        for (ServerNodeModel node : nodes) {
            if (node.getWeight() <= 0) {
                node.setDynamicWeight(averageWeight);
                totalWeight += averageWeight;
            } else {
                node.setDynamicWeight(node.getWeight());
                totalWeight += node.getWeight();
            }
        }
        double[] rateArray = new double[nodes.size()];
        for (int i = 0; i < nodes.size(); i++) {
            rateArray[i] = nodes.get(i).getDynamicWeight() / totalWeight;
        }
        Random r = new Random();
        double point = r.nextDouble();
        double range = 0d;
        for (int i = 0; i < rateArray.length; i++) {
            range += rateArray[i];
            if (point < range) {
                return nodes.get(i);
            }
        }
        return nodes.get(rateArray.length - 1);
    }

    static NodeGroupDTO loadByServiceName(String serviceName) {
        HashMap hashMap = new HashMap<String, String>();
        hashMap.put("serviceName", serviceName);

        try {
            String json = HttpHelper.postJsonToUrl(getNodesByNameUrl(), hashMap);
            NodeGroupDTO dto = JSON.parseObject(json, NodeGroupDTO.class);
            return dto;
        } catch (IOException e) {
            NodeGroupDTO dto = new NodeGroupDTO();
            dto.setNodes(new ArrayList<>());
            return dto;
        }
    }

    static class GetNodesUpdatesTimerTask extends TimerTask {
        @Override
        public void run() {
            List<GetNodesUpdateParams> data = Linq4j.asEnumerable(m_nodeDict.entrySet()).select(x -> {
                GetNodesUpdateParams ps = new GetNodesUpdateParams();
                ps.setServiceName(x.getKey());
                ps.setMaxEventId(x.getValue().getMaxEventId());
                return ps;
            }).toList();

            if (data.size() > 0) {
                try {
                    String json = HttpHelper.postJsonToUrl(getNodesUpdatesUrl(), data);
                    GetNodesUpdateResult result = JSON.parseObject(json, GetNodesUpdateResult.class);
                    ApplyEvents(result);
                } catch (IOException e) {
                    e.printStackTrace();
                    //log
                }
            }
        }
    }

    /// <summary>
    /// 应用事件
    /// </summary>
    /// <param name="result"></param>
    static void ApplyEvents(GetNodesUpdateResult result) {
        List<NodeEventModel> events = result.getEventList();
        if (events != null && events.size() > 0) {

            for (NodeEventModel evt : events) {
                NodeGroupDTO group = m_nodeDict.getOrDefault(evt.getServerNode().getServiceName(), null);
                if (group == null) {
                    group = new NodeGroupDTO();
                    group.setNodes(new ArrayList<>());
                    m_nodeDict.put(evt.getServerNode().getServiceName(), group);
                }
                group.setMaxEventId(result.getMaxEventId());

                ServerNodeModel localNode = Linq4j.asEnumerable(group.getNodes()).firstOrDefault(x -> x.getId() == evt.getServerNodeId());
                if (localNode != null) {
                    if (evt.getEventType() == NodeEventType.Logout || evt.getEventType() == NodeEventType.Delete) {
                        localNode.setStatus(ServerNodeStatus.Disabled);
                    } else if (evt.getEventType() == NodeEventType.Update || evt.getEventType() == NodeEventType.Register) {
                        localNode.setStatus(evt.getServerNode().getStatus());
                        localNode.setAddress(evt.getServerNode().getAddress());
                        localNode.setVersion(evt.getServerNode().getVersion());
                        localNode.setWeight(evt.getServerNode().getWeight());
                    }
                } else {
                    if (evt.getEventType() == NodeEventType.Register || evt.getEventType() == NodeEventType.Update) {
                        group.getNodes().add(evt.getServerNode());
                    }
                }
            }
        }
    }
}
